package org.nanotek.persistence.jpa;

import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;
import javax.validation.constraints.NotNull;

import org.nanotek.Base;
import org.nanotek.persistence.DAO;
import org.nanotek.persistence.DAOException;

/**
 * Base DAO Class for testing DomainModel serves as a decorator for the underlaying entity manager, and delegate 
 * to the Criteria "mainly a utility class" to speed up the persistent and access methods for JPA.
 * 
 * @author josecanovamauger
 *
 * @param <T>
 */
public abstract class JpaDAO<T extends Base<?>> implements DAO<T>{

	protected Class<T> baseClass; 
	
	@PersistenceContext
	protected EntityManager entityManager; 
	
	public JpaDAO() 
	{}

	public JpaDAO(Class<T> baseClass) {
		super();
		this.baseClass = baseClass;
	}

	public JpaDAO(Class<T> baseClass, EntityManager entityManager) {
		super();
		this.baseClass = baseClass;
		this.entityManager = entityManager;
	}

	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public Class<T> getBaseClass() {
		return baseClass;
	}

	@Override
	public void setBaseClass(Class<T> baseClass) {
		this.baseClass = baseClass;
	}
	
	
	@Override
	public void clear() {
		entityManager.clear();
	}

	@Override
	public boolean contains(T entity) {
		return entityManager.contains(entity);
	}

	@Override
	public Query createNamedQuery(String name) {
		return entityManager.createNamedQuery(name);
	}

	@Override
	public StoredProcedureQuery createNamedStoredProcedureQuery(String name) {
		return entityManager.createNamedStoredProcedureQuery(name);
	}

	
	@Override
	public Query createNativeQuery(String sqlString) {
		return entityManager.createNativeQuery(sqlString , this.baseClass);
	}
	
	//TODO: review class. 
	@Override
	public Query createNativeQuery(String sqlString, Class<T> resultClass) {
		return entityManager.createNativeQuery(sqlString, resultClass);
	}

	@Override
	public Query createNativeQuery(String sqlString, String resultSetMapping) {
		return entityManager.createNativeQuery(sqlString,resultSetMapping);
	}

	@Override
	public Query createQuery(String qlString) {
		return entityManager.createNamedQuery(qlString);
	}

	@Override
	public Query createQuery(CriteriaUpdate<T> updateQuery) {
		return entityManager.createQuery(updateQuery);
	}

	@Override
	public Query createQuery(CriteriaDelete<T> deleteQuery) {
		return entityManager.createQuery(deleteQuery);
	}

	@Override
	public StoredProcedureQuery createStoredProcedureQuery(
			String procedureName, String... resultSetMappings) {
		return entityManager.createStoredProcedureQuery(procedureName,resultSetMappings);
	}

	@Override
	public void detach(T entity) {
		entityManager.detach(entity);
	}

	@Override
	public T find(Object primaryKey) {
		return entityManager.find(baseClass,primaryKey);
	}

	@Override
	public T find(Object primaryKey, Map<String, Object> properties) {
		return entityManager.find(baseClass,primaryKey,properties);
	}

	@Override
	public void flush() {
		entityManager.flush();
	}

	@Override
	public T merge(T entity) {
		return entityManager.merge(entity);
	}

	/**
	*TODO: Change To the Transaction Context when necessary to change to Transaction Context.
	**/
	@Override
	public void persist(T entity) {
		try{ 
/*			EntityTransaction entityTransaction = entityManager.getTransaction();
			entityTransaction.begin();*/
				entityManager.persist(entity);
			/*entityTransaction.commit();*/
		}catch (Exception ex) 
		{ 
			throw new DAOException(ex);
		}
	}

	@Override
	public void refresh(T entity) {
		entityManager.refresh(entity);
	}

	@Override
	public void refresh(Map<String, Object> properties) {
		entityManager.refresh(baseClass, properties);
	}

	@Override
	public void remove(T entity) {
		entityManager.remove(entity);
	}

	
	@Override
	public abstract Long countRecords() throws DAOException;

	@Override
	public abstract List<T> loadAll()  throws DAOException;

	@Override
	public abstract List<T> listRecords(Integer firstResult, Integer maxResults)  throws DAOException;
	
	public <B extends Base<?>> CriteriaQuery <B> getCriteriaQuery(@NotNull Class<B> clazz) 
	{ 
		return getCriteriaBuilder().createQuery(clazz);
	}
	
	public CriteriaBuilder getCriteriaBuilder()
	{ 
		return  entityManager.getCriteriaBuilder();
	}
	
	public EntityType<T> getEntityType(){
		return entityManager.getMetamodel().entity(getBaseClass());
	}
	
	protected <K extends Base<?>> EntityType<K> getEntityType(CriteriaQuery<K> cq,Class<K> clazz)
	{ 
		Root<K> baseClassModel = cq.from(clazz);
		return baseClassModel.getModel();
	}

	protected <K extends Base<?>> Root<K> getRoot (Class<K> clazz)
	{ 
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<K> cq = cb.createQuery(clazz);
		return cq.from(clazz);
	}

}
